﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using SMSROUTER.Business;
using System.Threading.Tasks;
using Settings;
using StorageManagement;
using System.Configuration;
using System.Threading;
using System.Web.Services.Protocols;
using Utils;
using PluggableModulesInterface;
using System.Collections.Concurrent;

namespace PluggableModulesRouter
{
    public abstract class WorkingBaseRouter : IPluggableModule
    {
        #region Properties and Fields
        private int infinite_next_time = 100;
        protected virtual int INFINITE_NEXT_TIME
        {
            get { return infinite_next_time; }
            set { infinite_next_time = value; }
        }
        protected object _lock = new object();
        protected bool _IsStart = false;
        protected List<Task> taskList = new List<Task>();
        public int NumOfThread
        {
            get
            {
                try
                {
                    return int.Parse(GetConfig("NumOfThread"));
                }
                catch
                {
                    return 1;
                }
            }
            set
            {
                SetConfig("NumOfThread", value.ToString());
            }
        }
        protected int ThreadID
        {
            get
            {
                return Thread.CurrentThread.ManagedThreadId;
            }
        }
        protected string ObjectType { get; private set; }
        public int ServiceTimeOut
        {
            get
            {
                try
                {
                    return int.Parse(GetConfig("ServiceTimeOut"));
                }
                catch
                {
                    return AppSetting.ServiceTimeOut;
                }
            }
            set
            {
                SetConfig("ServiceTimeOut", value.ToString());
            }
        }
        public int BatchSize
        {
            get
            {
                try
                {
                    return int.Parse(GetConfig("BatchSize"));
                }
                catch
                {
                    return AppSetting.BatchSize;
                }
            }
            set
            {
                SetConfig("BatchSize", value.ToString());
            }
        }

        public int RetryOnTimeoutReqTimes
        {
            get
            {
                try
                {
                    return int.Parse(GetConfig("RetryOnTimeoutReqTimes"));
                }
                catch
                {
                    return AppSetting.RetryOnTimeoutReqTimes;
                }
            }
            set { SetConfig("RetryOnTimeoutReqTimes", value.ToString()); }
        }

        public int RetryOnFailReqTimes
        {
            get
            {
                try
                {
                    return int.Parse(GetConfig("RetryOnFailReqTimes"));
                }
                catch
                {
                    return AppSetting.RetryOnFailReqTimes;
                }
            }
            set { SetConfig("RetryOnFailReqTimes", value.ToString()); }
        }

        protected SmsRouter Router
        {
            get
            {
                return SmsRouter.GetByObjectType(ObjectType);
            }
        }

        public int Key { get; private set; }
        public string Name { get; private set; }
        public string Type { get; private set; }
        public bool Running
        {
            get
            {
                return (taskList.Count > 0);
            }
        }
        public string AssemblyLocation {private get; set; }

        public bool EnableLogToText
        {
            get
            {
                try
                {
                    return bool.Parse(GetConfig("EnableLogToText"));
                }
                catch
                {
                    return false;
                }
            }
            set
            {
                SetConfig("EnableLogToText", value.ToString());
            }
        }

        private ConcurrentDictionary<string, DateTime> currentException = new ConcurrentDictionary<string, DateTime>();
        #endregion Properties and Fields

        #region constructor
        public WorkingBaseRouter()
        {
            ObjectType = this.GetType().FullName;
            if (this.Router != null)
            {
                this.Key = Router.RouterID;
                this.Name = Router.RouterName;
                this.Type = this.Router.RouterType;
            }
        }
        #endregion constructor

        #region SyncLoop
        public abstract void Start();

        public void Stop()
        {
            if (_IsStart)
            {
                Log.InsertLog(string.Format("Router Stopped, Key: {0}, Name: {1}, Type: {2}", this.Key, this.Name, this.Type), "WorkingBaseRouter.Stop", ObjectType, AppSetting.WarningType.warning, null);
            }

            _IsStart = false;
            while (taskList.Count > 0)
            {
                taskList[0].Wait();
                taskList[0].Dispose();
                taskList.RemoveAt(0);
            }

            if (this.Router != null)
            {
                this.Router.Started = _IsStart;
                this.Router.Save();
            }
        }

        protected bool CheckRouter(ref string routerUrl)
        {
            SmsRouter router = this.Router;
            if (router != null && router.Enabled)
            {
                if (router.WebSerivceIsAvailable(ref routerUrl))
                {
                    if (this.NumOfThread > 0) return true;
                    else
                    {
                        throw new Exception("The number of threads in router " + Name + " must be positive number");
                    }
                }
                else
                {
                    throw new Exception(string.Format("{0} has died", Name));
                }
            }
            else
            {
                if (router == null)
                {
                    throw new Exception(string.Format("{0} does not exists", Name));
                }
                else if (!router.Enabled)
                {
                    throw new Exception(string.Format("{0} has disabled", Name));
                }
            }
            return false;
        }
        #endregion

        #region method
        public string GetAssemblyLocation()
        {
            return AssemblyLocation;
        }

        protected string GetConfig(string configKey)
        {
            return ConfigurationManager.OpenExeConfiguration(this.AssemblyLocation)
                    .AppSettings.Settings[configKey].Value;
        }

        protected void SetConfig(string configKey, string value)
        {
            Configuration configuration = ConfigurationManager.OpenExeConfiguration(this.AssemblyLocation);
            if (configuration.AppSettings.Settings[configKey] == null)
                configuration.AppSettings.Settings.Add(new KeyValueConfigurationElement(configKey, value));
            else
                configuration.AppSettings.Settings[configKey].Value = value;
            configuration.Save();
        }
        #endregion method

        #region Handle Exception
        protected virtual void HandleException(Exception ex, string logType)
        {
            string hashEx = ex.Message.ToMD5Hash();
            if (!currentException.ContainsKey(hashEx))
            {
                currentException.TryAdd(hashEx, DateTime.Now);
                Log.InsertLog(ex, this.ObjectType, logType, null);
            }
            else if (DateTime.Now - currentException[hashEx] >= TimeSpan.FromMilliseconds(AppSetting.AlertRouterDuration))
            {
                Log.InsertLog(ex, this.ObjectType, logType, null);
                currentException[hashEx] = DateTime.Now;
            }
            if (currentException.Count > 1000)
                currentException.Clear();
        }

        /// <summary>
        /// Xử lý Exception khi đang xử lý dữ liệu
        /// </summary>
        protected void HandleException<T>(Exception ex, string logType, List<T> objList)
        {
            HandleException(ex, logType);   
            if (ex is WebException)
            {
                if ((ex as WebException).Status == WebExceptionStatus.Timeout)
                {
                    RetryOnTimedOut(objList, ex.Message);
                }
                else
                {
                    RetryOnFailure(objList, ex.Message);
                }
            }
            else if (ex is SoapException)
            {
                RetryOnFailure(objList, ex.Message);
            }
            else if (ex is TimeoutException)
            {
                RetryOnTimedOut(objList, ex.Message);
            }
            else
            {
                RetryOnFailure(objList, ex.Message);
            }
        }

        /// <summary>
        /// Xử lý Exception khi đang xử lý dữ liệu
        /// </summary>
        protected void HandleException<T>(Exception ex, string logType, T obj)
        {
            HandleException(ex, logType);
            if (ex is WebException)
            {
                if ((ex as WebException).Status == WebExceptionStatus.Timeout)
                {
                    RetryOnTimedOut(obj, ex.Message);
                }
                else
                {
                    RetryOnFailure(obj, ex.Message);
                }
            }
            else if (ex is SoapException)
            {
                RetryOnFailure(obj, ex.Message);
            }
            else if (ex is TimeoutException)
            {
                RetryOnTimedOut(obj, ex.Message);
            }
            else
            {
                RetryOnFailure(obj, ex.Message);
            }
        } 

        /// <summary>
        /// Xử lý tin nhắn khi fail
        /// </summary>
        protected void RetryOnFailure<T>(IList<T> objList, string errorMessage)
        {
            if (objList == null || objList.Count == 0) return;
            foreach (var item in objList)
            {
                RetryOnFailure(item, errorMessage);
            }
        }

        /// <summary>
        /// Xử lý tin nhắn khi timed out
        /// </summary>
        protected void RetryOnTimedOut<T>(IList<T> objList, string errorMessage)
        {
            if (objList == null || objList.Count == 0) return;
            foreach (var item in objList)
            {
                RetryOnTimedOut(item, errorMessage);
            }
        }

        /// <summary>
        /// Xử lý tin nhắn khi fail
        /// </summary>
        protected abstract void RetryOnFailure(dynamic obj, string errorMessage);

        /// <summary>
        /// Xử lý tin nhắn khi timed out
        /// </summary>
        protected abstract void RetryOnTimedOut(dynamic obj, string errorMessage);
        #endregion Handle Exception

        #region Disposable
        // A base class that implements IDisposable. 
        // By implementing IDisposable, you are announcing that 
        // instances of this type allocate scarce resources. 

        // Pointer to an external unmanaged resource. 
        private IntPtr Ptrhandle;
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if (disposing)
                {
                    // Dispose managed resources.
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(Ptrhandle);
                Ptrhandle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;
            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~WorkingBaseRouter()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
        #endregion Dispose

        public void CollectGarbage()
        {
            GC.Collect();
        }
    }
}